import { InboxOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
import {
  SortableContext,
  arrayMove,
  horizontalListSortingStrategy,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import {
  App,
  Image,
  Spin,
  Tooltip,
  Upload,
  UploadFile,
  UploadProps,
  theme,
} from 'antd';
import lodash from 'lodash';
import React from 'react';
import SfwButton from '../../common/SfwButton';
import SfwFilePreview from '../../pro/SfwFilePreview';
import { cqrs, storage } from '../../utils';

// https://help.aliyun.com/zh/oss/user-guide/configure-the-content-type-header?spm=5176.28426678.J_HeJR_wZokYt378dwP-lLl.243.4f3d5181UkvaEF
export type OSSDataType = {
  ossDomain: string;
  accessId: string;
  signature: string;
  policy: string;
  expireTime: string;
};

export type UploadFileType = UploadFile & {
  url: string;
  name: string;
  size: number;
  type: string;
};

export type SfwAliyunOSSUploadProps = Omit<
  UploadProps,
  'value' | 'onChange'
> & {
  dir?: string;
  isAvatar?: boolean;
  isObjectValue?: boolean;
  readOnly?: boolean;
  draggable?: boolean;
  uploadButtonText?: string;
  value?: UploadFileType | UploadFileType[];
  onChange?: (value?: UploadFileType | UploadFileType[]) => void;
};

export type DraggableUploadListItemProps = {
  originNode: React.ReactElement<
    any,
    string | React.JSXElementConstructor<any>
  >;
  file: UploadFile;
} & SfwAliyunOSSUploadProps;

const DraggableUploadListItem = (props: DraggableUploadListItemProps) => {
  const { isObjectValue, readOnly, originNode, file } = props;
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: file.uid,
  });

  const style: React.CSSProperties = {
    height: '100%',
    transform: CSS.Translate.toString(transform),
    transition,
    cursor: isObjectValue ? undefined : 'move',
    marginTop: readOnly ? '-8px' : undefined,
  };

  return (
    <div
      ref={setNodeRef}
      style={style}
      // prevent preview event when drag end
      className={isDragging ? 'is-dragging' : ''}
      {...attributes}
      {...listeners}
    >
      {/* hide error tooltip when dragging */}
      {file.status === 'error' && isDragging
        ? originNode.props.children
        : originNode}
    </div>
  );
};

const imageTypes = [
  '.gif',
  '.png',
  '.apng',
  '.jpg',
  '.jpeg',
  '.bmp',
  '.ico',
  '.svg',
  '.tif',
  '.tiff',
  '.webp',
];

const getSuffix = (filename: string) => {
  return filename.slice(filename.lastIndexOf('.'));
};
const getOnlyName = (filename: string) => {
  return filename.slice(0, filename.lastIndexOf('.'));
};

// const encodeUrl = (url: string) => {
//   const filename = url.slice(url.lastIndexOf('/') + 1);
//   const urlPath = url.slice(0, url.lastIndexOf('/') + 1);
//   return `${urlPath}${encodeURIComponent(filename)}`;
// }

const transformValue = (file: UploadFile | UploadFile[] | any) => {
  // {
  //   "uid": "rc-upload-1718205642438-2",
  //   "lastModified": 1594909049833,
  //   "lastModifiedDate": "2020-07-16T14:17:29.833Z",
  //   "url": "https://oss.tgsc81.com/cscf/upload/n1(PQGi7W).jpeg",
  //   "name": "n1.jpeg",
  //   "size": 574373,
  //   "type": "image/jpeg",
  //   "percent": 100,
  //   "originFileObj": {
  //     "uid": "rc-upload-1718205642438-2",
  //     "url": "https://oss.tgsc81.com/cscf/upload/n1(PQGi7W).jpeg"
  //   },
  //   "status": "done",
  //   "response": "",
  //   "xhr": {}
  // }
  const getOne = (it: UploadFile) =>
    lodash.pick(it, 'url', 'name', 'size', 'type') as UploadFileType;
  if (lodash.isArray(file)) {
    return lodash.map(file, (it) => getOne(it));
  }
  return getOne(file as UploadFile);
};

const SfwAliyunOSSUpload: React.FC<SfwAliyunOSSUploadProps> = (props) => {
  const {
    dir = '/',
    isAvatar = false,
    isObjectValue = false,
    multiple = true,
    readOnly = false,
    draggable = false,
    uploadButtonText = '上传',
    maxCount = 9,
    value,
    onChange,
    ...restProps
  } = props;
  const { message } = App.useApp();
  const { token } = theme.useToken();
  const isUploadMultiple = !isObjectValue;
  const [loading, setLoading] = React.useState<boolean>(true);
  const [ossData, setOssData] = React.useState<OSSDataType>();
  const fileListValue = (
    isUploadMultiple ? value : !value ? undefined : [value]
  ) as UploadFile[];
  const [fileList, setFileList] = React.useState<UploadFile[]>(fileListValue);
  const fileImageListUrl = fileList
    ?.filter((it) => lodash.includes(imageTypes, getSuffix(it.name)))
    .map((it) => it.url) as string[];
  const [previewOpen, setPreviewOpen] = React.useState(false);
  const [previewFile, setPreviewFile] = React.useState<UploadFile>();

  // https://help.aliyun.com/document_detail/31988.html
  const getOssData = async () => {
    // {
    //   "ossDomain": "https://oss.huanguanjia.cn",
    //   "accessId": "LTAI4GGPGvxt2CpFjefWU2j7",
    //   "signature": "+/th/5vXLt/tdPxuGMra9iORBRE=",
    //   "policy": "eyJleHBpcmF0aW9uIjoiMjAyNS0wNi0xMlQwMDowMDozMy43MTdaIiwiY29uZGl0aW9ucyI6W3t9XX0=",
    //   "expireTime": "2025-06-12T00:00:33.717Z"
    // }
    const cacheKey = `sfw4_cache_uploadAliyunOssSignature`;
    const cachedData = storage.getItem<OSSDataType>(cacheKey);
    if (cachedData) {
      const expire = window.sfw.dayjs(cachedData.expireTime);
      if (!expire.isBefore(window.sfw.dayjs())) {
        // 如果缓存存在且未过期，直接返回缓存数据
        setLoading(false);
        return Promise.resolve(cachedData);
      }
    }

    return cqrs
      .queryForForm({
        body: {
          code: 'OssSignatureQry',
        },
      })
      .then((result) => {
        if (result.error) {
          setLoading(false);
          message.error(`获取文件上传签名错误，请稍后再试`);
          return;
        }
        setLoading(false);
        return result;
      })
      .then((result) => {
        if (!result.error) {
          // 更新缓存
          storage.setItem(cacheKey, result);
        }
        return result;
      });
  };

  const init = async () => {
    const result = await getOssData();
    setOssData(result);
  };

  React.useEffect(() => {
    init();
  }, []);

  const sensor = useSensor(PointerSensor, {
    activationConstraint: { distance: 10 },
  });

  const handleChange: UploadProps['onChange'] = (info) => {
    const { fileList } = info;
    if (info.file.status === 'uploading') {
      setLoading(true);
      setFileList(fileList);
      return;
    }
    if (info.file.status !== 'done') {
      setLoading(false);
      setFileList(fileList);
      return;
    }

    // done
    setLoading(false);
    if (!isUploadMultiple) {
      setFileList([info.file]);
      onChange?.(transformValue(info.file));
      return;
    }
    setFileList(fileList);
    onChange?.(transformValue([...fileList]));
  };

  const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      // file.preview = await getBase64(file.originFileObj as FileType);
    }

    setPreviewFile(file);
    setPreviewOpen(true);
  };

  const handleRemove = (file: UploadFile) => {
    if (!isUploadMultiple) {
      onChange?.(undefined);
    }
    const files = ((value as UploadFile[]) || []).filter(
      (v) => v.url !== file.url,
    );

    onChange?.(transformValue(files));
  };

  const handleDragEnd = ({ active, over }: DragEndEvent) => {
    if (active.id !== over?.id) {
      const newFileListFun = (prev: any) => {
        const activeIndex = prev.findIndex((i: any) => i.uid === active.id);
        const overIndex = prev.findIndex((i: any) => i.uid === over?.id);
        return arrayMove(prev, activeIndex, overIndex);
      };
      const newFileList = newFileListFun(fileList) as any;
      setFileList(newFileList);
      onChange?.(transformValue([...newFileList]));
    }
  };

  const getExtraData: UploadProps['data'] = (file) => ({
    // @ts-ignore
    key: lodash.replace(file.url, ossData?.ossDomain, '').replace(/^\//, ''),
    OSSAccessKeyId: ossData?.accessId,
    policy: ossData?.policy,
    Signature: ossData?.signature,
  });

  const beforeUpload: UploadProps['beforeUpload'] = async (file) => {
    if (!ossData) return false;

    const expire = window.sfw.dayjs(ossData.expireTime);
    if (expire.isBefore(window.sfw.dayjs())) {
      await init();
    }

    const name = getOnlyName(file.name);
    const suffix = lodash.toLower(getSuffix(file.name));
    // const filename = `${window.sfw.dayjs().format('YYYYMMDDHHmmss')}(${window.sfw.sfwUtils.generateRandomString(6)})${suffix}`;
    const filename = `${name}(${window.sfw.sfwUtils.generateRandomString(
      6,
    )})${suffix}`;
    // @ts-ignore
    file.url = `${ossData?.ossDomain}${dir}/${filename}`;

    return file;
  };

  const listType = restProps.listType || 'text';
  const showUploadList = isAvatar
    ? false
    : readOnly
    ? {
        showPreviewIcon: true,
        showRemoveIcon: false,
      }
    : restProps.showUploadList;
  const uploadProps: UploadProps = {
    action: `${ossData?.ossDomain}`,
    name: 'file',
    maxCount,
    multiple,
    fileList,
    showUploadList,
    onChange: handleChange,
    onPreview: handlePreview,
    onRemove: handleRemove,
    beforeUpload,
    data: getExtraData,
    itemRender: (originNode, file) => (
      <DraggableUploadListItem {...props} originNode={originNode} file={file} />
    ),
    ...restProps,
  };

  const uploadButton = () => {
    if (readOnly) return null;
    if (maxCount !== 1 && fileList?.length >= maxCount) return null;

    if (listType === 'text' || listType === 'picture') {
      return (
        <SfwButton
          icon={<UploadOutlined />}
          loading={loading}
          disabled={lodash.isEmpty(ossData)}
        >
          {uploadButtonText}
        </SfwButton>
      );
    }

    const borderRadius = listType === 'picture-circle' ? '50%' : undefined;
    const avatar = lodash.head(fileList);
    return (
      <Tooltip title={'点击上传'}>
        {/*{isAvatar && !loading && avatar ? (*/}
        {isAvatar && avatar ? (
          <div
            className={'ant-upload-list-item-container'}
            style={{
              padding: token.paddingXS,
              height: '100%',
            }}
          >
            <div
              className={'ant-upload-list-item'}
              style={{
                height: '100%',
                borderRadius,
                overflow: 'hidden',
              }}
            >
              <img
                alt="avatar"
                src={avatar.url}
                style={{
                  width: '100%',
                  height: '100%',
                  objectFit: 'cover',
                }}
              />
            </div>
          </div>
        ) : (
          <button style={{ border: 0, background: 'none' }} type="button">
            <PlusOutlined />
            <div style={{ marginTop: 8 }}>{uploadButtonText}</div>
          </button>
        )}
      </Tooltip>
    );
  };

  const uploadDom = <Upload {...uploadProps}>{uploadButton()}</Upload>;
  const uploadAndDraggerDom = draggable ? (
    <Upload.Dragger {...uploadProps}>
      <p className="ant-upload-drag-icon">
        <InboxOutlined />
      </p>
      <p className="ant-upload-text">单击或拖动文件到此区域进行上传</p>
    </Upload.Dragger>
  ) : (
    uploadDom
  );

  return (
    <>
      <Spin spinning={loading} size={'small'}>
        {readOnly ? (
          uploadDom
        ) : isObjectValue ? (
          uploadAndDraggerDom
        ) : (
          <DndContext sensors={[sensor]} onDragEnd={handleDragEnd}>
            <SortableContext
              items={fileList?.map((i) => i.uid) || []}
              strategy={
                lodash.includes(['text', 'picture'], listType)
                  ? verticalListSortingStrategy
                  : horizontalListSortingStrategy
              }
            >
              {uploadAndDraggerDom}
            </SortableContext>
          </DndContext>
        )}
      </Spin>
      {previewFile &&
        (lodash.includes(imageTypes, getSuffix(previewFile.name)) ? (
          <Image.PreviewGroup
            preview={{
              visible: previewOpen,
              onVisibleChange: (visible) => setPreviewOpen(visible),
              current: lodash.indexOf(fileImageListUrl, previewFile?.url),
              onChange: (current: number) => {
                const fileIndex = lodash.findIndex(fileList, (it) =>
                  lodash.isEqual(it.url, fileImageListUrl?.[current]),
                );
                setPreviewFile(fileList?.[fileIndex]);
              },
            }}
            items={fileImageListUrl}
          />
        ) : (
          <SfwFilePreview
            file={previewFile as any}
            open={previewOpen}
            onCancel={() => setPreviewFile(undefined)}
          />
        ))}
    </>
  );
};

export default SfwAliyunOSSUpload;
